﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using Milkify.Data;
using Milkify.Diagnostics;
using Milkify.Interfaces;
using Milkify.Input;

namespace Milkify.Views
{
    public partial class MainWindow : Window, IMainWindowView, IViewContainer
    {
        #region Private Members
        private Window mHelperWindow;

        private ResizeHelper mResizeHelper;
        private List<object> mBackNavigationJournal = new List<object>();

        private object mProgressRequestLock = new object();
        #endregion

        #region Construction & Initialization
        public MainWindow()
        {
            InitializeComponent();
            InitializeHelperWindow();
            InitializeNavigationHandler();
            InitializeResizeHelper();
            InitializeDebugging();
        }

        private void InitializeHelperWindow()
        {
            mHelperWindow = new Window();
            mHelperWindow.Left = -100;
            mHelperWindow.Top = -100;
            mHelperWindow.Width = 1;
            mHelperWindow.Height = 1;
            mHelperWindow.WindowStyle = WindowStyle.ToolWindow;

            Closed += new EventHandler(MainWindow_Closed);
            StateChanged += new EventHandler(MainWindow_StateChanged);
            Activated += new EventHandler(MainWindow_Activated);
        }

        private void InitializeNavigationHandler()
        {
            ViewFrame.NavigationService.Navigated += new NavigatedEventHandler(FrameNavigationService_Navigated);
        }

        private void InitializeResizeHelper()
        {
            mResizeHelper = new ResizeHelper(this);
            mResizeHelper.AddBorderElement(ResizeLeft, BorderType.Left);
            mResizeHelper.AddBorderElement(ResizeTopLeft, BorderType.Left | BorderType.Top);
            mResizeHelper.AddBorderElement(ResizeTop, BorderType.Top);
            mResizeHelper.AddBorderElement(ResizeTopRight, BorderType.Right | BorderType.Top);
            mResizeHelper.AddBorderElement(ResizeRight, BorderType.Right);
            mResizeHelper.AddBorderElement(ResizeBottomRight, BorderType.Right | BorderType.Bottom);
            mResizeHelper.AddBorderElement(ResizeBottom, BorderType.Bottom);
            mResizeHelper.AddBorderElement(ResizeBottomLeft, BorderType.Left | BorderType.Bottom);
        }

        private void InitializeDebugging()
        {
            Action<object, KeyboardFocusChangedEventArgs> handler = (sender, e) => MilkifyTraceSources.UISource.TraceEvent(TraceEventType.Verbose, 0, "{0} got focus from {1}", e.NewFocus, e.OldFocus);
            GotKeyboardFocus += new KeyboardFocusChangedEventHandler(handler);
        }
        #endregion

        #region Event Handlers
        #region Window Events
        private void MainWindow_StateChanged(object sender, EventArgs e)
        {
            // This is a hack so that the Milkify window doesn't show up
            // in the ALT-TAB dialog when it's minimized to tray.
            if (WindowState == WindowState.Minimized)
            {
                if (mHelperWindow != null)
                {
                    mHelperWindow.Show();
                    Owner = mHelperWindow;
                    mHelperWindow.Hide();
                }
            }
            else
            {
                Owner = null;
            }
        }
        private void MainWindow_Closed(object sender, EventArgs e)
        {
            // Dispose of the window hack (see MainWindow_StateChanged) so that
            // the application correctly closes.
            if (mHelperWindow != null)
            {
                mHelperWindow.Close();
                mHelperWindow = null;
            }
        }

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            // Kind of a hacky way to keep the window pinned to the desktop... on activated, put it back at the bottom!
            //TODO: maybe find a better way?
            if (mIsPinnedToDesktop)
            {
                var helper = new System.Windows.Interop.WindowInteropHelper(this);
                SetWindowPos(helper.Handle, (IntPtr)HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
            }
        }
        #endregion

        #region Title Bar
        private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!mIsPinnedToDesktop)
                DragMove();
        }

        private void MinimizeButton_Click(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

        private void CloseButton_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
        #endregion

        #region Search Field
        private void TextSearch_GotFocus(object sender, RoutedEventArgs e)
        {
            ((TextBox)sender).SelectAll();
        }

        private void TextSearch_GotMouseCapture(object sender, MouseEventArgs e)
        {
            ((TextBox)sender).SelectAll();
        }

        private void TextSearch_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                string query = ((TextBox)sender).Text;
                if (ViewData != null)
                    ((MainWindowViewData)ViewData).SearchTaskCommand.Execute(query);

                e.Handled = true;
            }
            else if (e.Key == Key.Escape)
            {
                Keyboard.Focus(null);

                e.Handled = true;
            }
        }
        #endregion

        #region Navigation Events
        private void FrameNavigationService_Navigated(object sender, NavigationEventArgs e)
        {
            if (mBackNavigationJournal.Count > 1)
            {
                if (e.Content == mBackNavigationJournal[mBackNavigationJournal.Count - 2])
                    mBackNavigationJournal.RemoveAt(mBackNavigationJournal.Count - 1);
                else
                    mBackNavigationJournal.Add(e.Content);
            }
            else
            {
                mBackNavigationJournal.Add(e.Content);
            }
        }
        #endregion
        #endregion

        #region IViewContainer Members

        private bool mIsPinnedToDesktop = false;
        public bool IsPinnedToDesktop
        {
            get { return mIsPinnedToDesktop; }
            set
            {
                if (value != mIsPinnedToDesktop)
                {
                    if (value)
                    {
                        var helper = new System.Windows.Interop.WindowInteropHelper(this);
                        SetWindowPos(helper.Handle, (IntPtr)HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
                    }

                    MinimizeButton.IsEnabled = !value;
                    ShowActivated = !value;
                    ShowInTaskbar = !value;

                    mIsPinnedToDesktop = value;
                }
            }
        }

        public string UserName
        {
            get { return TextUserName.Text; }
            set { TextUserName.Text = value; }
        }

        public IView CurrentView
        {
            get { return (IView)ViewFrame.Content; }
        }

        public bool CanNavigateBack
        {
            get { return ViewFrame.NavigationService.CanGoBack; }
        }

        public int RequestProgressMaximum
        {
            get
            {
                lock (mProgressRequestLock)
                {
                    return (int)ProgressRequests.Maximum;
                }
            }
            set
            {
                lock (mProgressRequestLock)
                {
                    ProgressRequests.Maximum = value;
                    OnProgressRequestChange();
                }
            }
        }

        public int RequestProgressCurrent
        {
            get
            {
                lock (mProgressRequestLock)
                {
                    return (int)ProgressRequests.Value;
                }
            }
            set
            {
                lock (mProgressRequestLock)
                {
                    ProgressRequests.Value = value;
                    OnProgressRequestChange();
                }
            }
        }

        private void OnProgressRequestChange()
        {
            if (ProgressRequests.Value >= ProgressRequests.Maximum)
            {
                Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(TraceEventType.Verbose, 0, "Resetting the progress bar to zero from ({0} / {1}).", ProgressRequests.Value, ProgressRequests.Maximum);

                ProgressRequests.Visibility = Visibility.Collapsed;
                ProgressRequests.Maximum = 0;
                ProgressRequests.Value = 0;
            }
            else
            {
                Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(TraceEventType.Verbose, 0, "Updating progress to ({0} / {1}).", ProgressRequests.Value, ProgressRequests.Maximum);
                
                ProgressRequests.Visibility = Visibility.Visible;
            }
        }

        public event NavigatingCancelEventHandler Navigating
        {
            add { ViewFrame.Navigating += value; }
            remove { ViewFrame.Navigating -= value; }
        }

        public event NavigatedEventHandler Navigated
        {
            add { ViewFrame.Navigated += value; }
            remove { ViewFrame.Navigated -= value; }
        }

        public void NavigateBack()
        {
            ViewFrame.NavigationService.GoBack();
        }

        public void NavigateToView(IView view)
        {
            ViewFrame.Navigate(view);
        }

        public void InvalidateRefreshableViews()
        {
            foreach (var content in mBackNavigationJournal)
            {
                IRefreshableView view = content as IRefreshableView;
                if (view != null)
                    view.NeedsRefresh = true;
            }
        }

        #endregion

        #region IMainWindowView Members

        Point IMainWindowView.Location
        {
            get { return new Point(Left, Top); }
            set
            {
                Left = value.X;
                Top = value.Y;
            }
        }

        Size IMainWindowView.Size
        {
            get { return new Size(Width, Height); }
            set
            {
                Width = value.Width;
                Height = value.Height;
            }
        }

        void IMainWindowView.Show()
        {
            Show();
            WindowState = WindowState.Normal;
        }

        void IMainWindowView.FocusSearchBox()
        {
            Keyboard.Focus(TextSearch);
        }

        #endregion

        #region IView Members

        public object ViewData
        {
            get { return DataContext; }
            set { DataContext = value; }
        }

        #endregion

        #region Interop
        private const int SWP_ASYNCWINDOWPOS = 0x4000;
        private const int SWP_DEFERERASE = 0x2000;
        private const int SWP_DRAWFRAME = 0x0020;
        private const int SWP_FRAMECHANGED = 0x0020;
        private const int SWP_HIDEWINDOW = 0x0080;
        private const int SWP_NOACTIVATE = 0x0010;
        private const int SWP_NOCOPYBITS = 0x0100;
        private const int SWP_NOMOVE = 0x0002;
        private const int SWP_NOOWNERZORDER = 0x0200;
        private const int SWP_NOREDRAW = 0x0008;
        private const int SWP_NOREPOSITION = 0x0200;
        private const int SWP_NOSENDCHANGING = 0x0400;
        private const int SWP_NOSIZE = 0x0001;
        private const int SWP_NOZORDER = 0x0004;
        private const int SWP_SHOWWINDOW = 0x0040;

        private const int HWND_TOP = 0;
        private const int HWND_BOTTOM = 1;
        private const int HWND_TOPMOST = -1;
        private const int HWND_NOTOPMOST = -2;

        [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
        [return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
        #endregion
    }
}
